from flask import Blueprint,g,jsonify
from flask_restful import Resource,marshal_with,fields,reqparse,inputs,marshal
from app.extensions import api,db
from app.models import NewsType,News
from app.util import login_required
import json # 从flask导入的json在json.dumps()里输参数ensure_ascii=False时没自动补全

news=Blueprint('news',__name__) # 创建蓝本对象，第一个参数是蓝本名，不能与其他蓝本名重复；第二个参数是要导入蓝本的模块的名；第三个参数是统一给蓝本里路由添加的前缀，可不写
api.init_app(news) # 将蓝图绑定到api，而不是以前的app。若蓝图设了url_prefix，则访问api绑定的路由时要加上蓝图前缀(可在app的__init__.py中打印app.url_map查看)。from https://flask-restful.readthedocs.io/en/latest/intermediate-usage.html#use-with-blueprints


class AuthorName(fields.Raw): # 自定义fields，需继承自Raw，因fields.Integer和fields.String等也继承自Raw。from https://flask-restful.readthedocs.io/en/latest/fields.html#custom-fields-multiple-values
    def format(self,value):
        return value.username


parser=reqparse.RequestParser()
parser.add_argument('type_id',type=int,help='请选择新闻类型',required=True) # 接收新闻类型的id
parser.add_argument('page',type=int)
add_type_parser=reqparse.RequestParser()
add_type_parser.add_argument('type_name',type=str,help='请填写要添加的新闻类型名',required=True,location='form') # 接收要添加的新闻类型名
modify_type_parser=add_type_parser.copy()
modify_type_parser.add_argument('type_id',type=int,help='请填写要修改的新闻类型的id',required=True) # 接收要修改的新闻类型的id
delete_type_parser=reqparse.RequestParser()
delete_type_parser.add_argument('type_id',type=int,help='请填写要删除的新闻类型的id',required=True) # 接收要删除的新闻类型的id
add_news_parser=reqparse.RequestParser()
add_news_parser.add_argument('title',type=str,help='请输入新闻标题',required=True,location='form') # 接收要发布的新闻的标题
add_news_parser.add_argument('content',type=str,help='请输入新闻内容',required=True,location='form') # 接收要发布的新闻的内容
add_news_parser.add_argument('type_id',type=int,help='请选择新闻类型',required=True,location='form') # 接收要发布的新闻的类型的id
types_fields={
    'id':fields.Integer,
    'name':fields.String(attribute='type') # 该字段在数据库中是type
}
reply_fields={ # 对评论的回复的格式
    'user':AuthorName(attribute='user'),
    'content':fields.String,
    'datetime':fields.DateTime(attribute='timestamp'),
    'star_times':fields.Integer
}
comment_fields={ # 评论的格式
    'user':AuthorName(attribute='user'),
    'content':fields.String,
    'datetime':fields.DateTime(attribute='timestamp'),
    'star_times':fields.Integer,
    'replies':fields.List(fields.Nested(reply_fields)) # 对评论的回复
}
news_fields={ # 新闻列表中每条新闻的格式
    'id':fields.Integer,
    'title':fields.String,
    'datetime':fields.DateTime(attribute='timestamp'),
    'author':AuthorName(attribute='author'), # 用自定义fields实现自定义返回值，这里调用时方法中的value的值就是User模型中反向引用的名字
    'uri':fields.Url('news.news_detail',absolute=True) # 不知道为什么要加news.，这里不加会报错，上一个项目没加不报错。第一个参数是endpoint名，absolute为True返回完整链接，为False返回不含域名的路由，参数scheme设为'https'可以返回https的链接。找到这个名字绑定的路由，根据路由生成对应的链接。from https://flask-restful.readthedocs.io/en/latest/fields.html#url-other-concrete-fields
}
news_detail_fields={ # 新闻详情的格式，即点击某条新闻后展示的完整内容
    'id':fields.Integer,
    'title':fields.String,
    'content':fields.String, # 数据库中是Text类型，fields中无Text类型，可用String代替
    'datetime':fields.DateTime(attribute='timestamp'),
    'author':AuthorName(attribute='author'),
    'comments':fields.List(fields.Nested(comment_fields)) # comments里是NewsComment对象，不能直接放在fields.List()里，fields.Nested()支持嵌套形式的json，里面传入字典，会把NewsComment对象转成前面传入的字典的格式，然后放在List里。from https://flask-restful.readthedocs.io/en/latest/fields.html#list-field和https://flask-restful.readthedocs.io/en/latest/fields.html#advanced-nested-field
}


class NewsTypeResource(Resource): # 新闻类型的类视图
    @marshal_with(types_fields)
    def get(self): # 获取所有分类
        types=NewsType.query.all()
        return types

    def post(self): # 添加新闻分类
        args=add_type_parser.parse_args()
        type_name=args.get('type_name')
        new_type=NewsType(type=type_name)
        db.session.add(new_type)
        db.session.commit()
        return marshal(new_type,types_fields)

    def patch(self): # 修改新闻分类名。put是修改整体，patch是修改部分
        args=modify_type_parser.parse_args()
        type_id=args.get('type_id')
        new_type_name=args.get('type_name')
        t=NewsType.query.get(type_id)
        if t:
            t.type=new_type_name
            db.session.commit()
            data={
                'message':'新闻分类名修改成功',
                'status':200,
                'type':marshal(t,types_fields)
            }
        else:
            data={
                'error':'不存在该新闻分类',
                'status':400,
            }
        return data

    def delete(self): # 删除新闻分类
        args=delete_type_parser.parse_args()
        type_id=args.get('type_id')
        t=NewsType.query.get(type_id)
        if t:
            db.session.delete(t)
            db.session.commit()
            data={
                'message':'新闻分类删除成功',
                'status':200
            }
        else:
            data={
                'error':'不存在该新闻分类',
                'status':400
            }
        return data


class NewsListResource(Resource): # 新闻列表的类视图，点击新闻类型时，返回某类的多条新闻
    def get(self):
        args=parser.parse_args()
        type_id=args.get('type_id')
        page=args.get('page',1) # 取不到(前端未传给后端)时返回1
        pagination=News.query.filter(News.type_id==type_id).paginate(page=page,per_page=8)
        data={
            'has_more':pagination.has_next, # 是否有下一页
            'data':marshal(pagination.items,news_fields), # 当页的数据，是一个个新闻对象，数量由per_page决定
            'return_count':len(pagination.items),
            'html':'null'
        }
        return data


class NewsDetailResource(Resource): # 新闻详情的类视图
    @marshal_with(news_detail_fields)
    def get(self,id): # 点击某条新闻时用，展示新闻
        news=News.query.get(id)
        return news


class NewsResource(Resource): # 新闻的类视图
    @login_required # 自定义的装饰器
    def post(self): # 发表新闻。postman里要在Headers加上请求头,KEY是Authorization，VALUE是用户登录后返回的token。postman的Authorization的Type要为No Auth，否则会跟Headers中的Authorization冲突，导致获取不到正确的token
        args=add_news_parser.parse_args()
        title=args.get('title')
        content=args.get('content')
        type_id=args.get('type_id')
        news=News(title=title,content=content,type_id=type_id,user_id=g.user.id)
        db.session.add(news)
        db.session.commit()
        data={
            'message':'新闻发表成功',
            'status':200,
            'news':marshal(news,news_detail_fields)
        }
        return data # 不同的返回方式的返回值的编码，已写博客
        # return jsonify(message='新闻发表成功',status=200,news=marshal(news,news_detail_fields))
        # return json.dumps(data)
        # return json.dumps(data,ensure_ascii=False)

    @login_required
    def patch(self): # 修改新闻
        pass

    @login_required
    def put(self): # 修改新闻
        pass

    @login_required
    def delete(self): # 删除新闻
        pass


# 接口功能和流程仿今日头条
api.add_resource(NewsTypeResource,'/types') # 若蓝图设了url_prefix，则访问api绑定的路由时要加上蓝图前缀(可在app的__init__.py中打印app.url_map查看)，前提是蓝图绑定到api，而不是以前的app。
api.add_resource(NewsListResource,'/news_list')
api.add_resource(NewsDetailResource,'/news_detail/<int:id>',endpoint='news_detail')
api.add_resource(NewsResource,'/news') # 注意已设置需先登录才能用这个接口